import { WSDispatcher } from './WSDispatcher';

export interface IRequestPayload {
    type: string;
    data: any;
    requestId?: string;
}

export interface PendingRequest {
    resolve: (data: any) => void;
    reject: (err: any) => void;
    timeoutId: any;
}

export interface IBinaryRequestPayload {
    type: string;
    requestId?: string;
    meta?: any; // 可选元信息
}

export type MessageHandler = (data: any) => void;

export type RequestProcessor = (data: any) => any | Promise<any>;

/**
 * WsClient: 基于 Laya.Socket 的通用 WebSocket 客户端
 * 功能：
 *  - 自动重连（可配置次数与延迟）
 *  - 心跳机制（ping 保活）
 *  - 统一消息分发 & 通知处理（WSDispatcher）
 *  - 请求-响应模型（requestId 一一对应，超时控制）
 *  - 双向请求：服务端可请求客户端，客户端处理并自动应答
 *  - 资源释放：手动关闭或重连失败时清理挂起请求
 */
export class WsClient {
    private socket: Laya.Socket;
    private url: string = '';
    private manuallyClosed: boolean = false;

    private autoReconnect = true;
    private reconnectDelay = 3000;
    private maxReconnectAttempts = 5;
    private currentReconnectAttempts = 0;

    private heartbeatInterval = 10000;
    private heartbeatEnabled = false;
    private heartbeatTimer: any = null;

    private requestIdCounter = 0;
    private wsDispatcher = new WSDispatcher();

    public onConnectEvent?: Laya.Handler;
    public onDisconnectEvent?: Laya.Handler;

    /** 建立连接（URL） */
    public connectByUrl(url: string): void {
        this.url = url;
        this.manuallyClosed = false;
        this.initSocket();
        this.socket.connectByUrl(url);
    }

    /** 连接 host & port */
    public connect(host: string, port: number, isSecure?: boolean): void {
        this.manuallyClosed = false;
        this.initSocket();
        this.socket.connect(host, port, isSecure);
    }

    /** 手动关闭 */
    public close(): void {
        this.manuallyClosed = true;
        this.stopHeartbeat();
        this.socket?.close();
    }

    /** 配置重连策略 */
    public setReconnect(maxAttempts: number, delayMs: number): void {
        this.maxReconnectAttempts = maxAttempts;
        this.reconnectDelay = delayMs;
    }

    /** 配置心跳 */
    public setHeartbeat(intervalMs: number, enabled: boolean): void {
        this.heartbeatInterval = intervalMs;
        this.heartbeatEnabled = enabled;
        if (this.socket?.connected && enabled) {
            this.startHeartbeat();
        }
    }

    /** 注册通知型消息 */
    public onMessageType(type: string, handler: (data: any) => void): void {
        this.wsDispatcher.registerMessage(type, handler);
    }

    /** 注销通知型消息 */
    public offMessageType(type: string): void {
        this.wsDispatcher.unregisterMessage(type);
    }

    /** 注册服务端请求处理器 */
    public registerRequestProcessor(type: string, processor: (data: any) => any | Promise<any>): void {
        this.wsDispatcher.registerProcessor(type, processor);
    }

    /** 注销请求处理器 */
    public unregisterRequestProcessor(type: string): void {
        this.wsDispatcher.unregisterProcessor(type);
    }

    /**
     * 发送请求并返回 Promise，超时自动 reject
     */
    public sendRequest(type: string, data: any, timeout: number = 10000): Promise<any> {
        const requestId = (++this.requestIdCounter).toString();
        const payload = JSON.stringify({ type, data, requestId });
        return new Promise((resolve, reject) => {
            const timeoutId = setTimeout(() => {
                this.wsDispatcher.unregisterPendingRequest(requestId);
                reject(new Error(`Request "${type}" timed out`));
            }, timeout);

            this.wsDispatcher.registerPendingRequest(requestId, { resolve, reject, timeoutId });
            this.sendRaw(payload);
        });
    }

    /** 发送通知，无需响应 */
    public sendNotify(type: string, data: any): void {
        this.sendRaw(JSON.stringify({ type, data }));
    }

    /** 初始化 WebSocket 及事件绑定 */
    private initSocket(): void {
        this.socket = new Laya.Socket();
        this.socket.on(Laya.Event.OPEN, this, this.onOpen);
        this.socket.on(Laya.Event.MESSAGE, this, this.onMessage);
        this.socket.on(Laya.Event.CLOSE, this, this.onClose);
        this.socket.on(Laya.Event.ERROR, this, this.onError);
    }

    private onOpen(): void {
        console.log('[WS] Connected');
        this.currentReconnectAttempts = 0;
        if (this.heartbeatEnabled) this.startHeartbeat();
        this.onConnectEvent?.run();
    }

    private onClose(): void {
        console.warn('[WS] Connection closed');
        this.stopHeartbeat();
        this.onDisconnectEvent?.run();

        if (!this.manuallyClosed && this.autoReconnect) {
            if (this.currentReconnectAttempts < this.maxReconnectAttempts) {
                this.currentReconnectAttempts++;
                setTimeout(() => this.connectByUrl(this.url), this.reconnectDelay);
            } else {
                this.cleanupPendingRequests('Reconnect failed');
            }
        } else {
            this.cleanupPendingRequests('Connection closed');
        }
    }

    private onError(err: any): void {
        console.error('[WS] Error:', err);
        this.onDisconnectEvent?.runWith(err);
    }

    /** 核心消息处理 */
    private async onMessage(raw: any): Promise<void> {
        if (raw === null) return;
        if (raw instanceof ArrayBuffer) {
            const view = new DataView(raw);
            const metaLength = view.getUint32(0, true);
            const metaBytes = new Uint8Array(raw, 4, metaLength);
            const metaString = new TextDecoder().decode(metaBytes);
            const meta = JSON.parse(metaString) as IBinaryRequestPayload;
            const binaryData = raw.slice(4 + metaLength);
            const { type, requestId } = meta;

            if (requestId && this.wsDispatcher.hasPendingRequest(requestId)) {
                const pending = this.wsDispatcher.getPendingRequest(requestId)!;
                clearTimeout(pending.timeoutId);
                this.wsDispatcher.unregisterPendingRequest(requestId);
                pending.resolve({ meta, data: binaryData });
                return;
            }

            if (requestId && this.wsDispatcher.hasProcessor(type)) {
                try {
                    const result = await this.wsDispatcher.processRequest(type, { meta, data: binaryData });
                    this.sendRaw(JSON.stringify({ type, requestId, data: result }));
                } catch (err) {
                    this.sendRaw(JSON.stringify({ type, requestId, data: { error: (err as Error).message } }));
                }
                return;
            }

            this.wsDispatcher.dispatchMessage(type, { meta, data: binaryData });
        } else if (typeof raw === 'string') {
            console.log('[WS] Receive:', JSON.stringify(raw));
            let msg: IRequestPayload;
            try {
                msg = JSON.parse(raw);
            } catch (e) {
                return;
            }
            const { type, data, requestId } = msg;

            // 1. 响应请求
            if (requestId && this.wsDispatcher.hasPendingRequest(requestId)) {
                const pending = this.wsDispatcher.getPendingRequest(requestId)!;
                clearTimeout(pending.timeoutId);
                this.wsDispatcher.unregisterPendingRequest(requestId);
                pending.resolve(data);
                return;
            }

            // 2. 服务端发起请求
            if (requestId && this.wsDispatcher.hasProcessor(type)) {
                try {
                    const result = await this.wsDispatcher.processRequest(type, data);
                    this.sendRaw(JSON.stringify({ type, requestId, data: result }));
                } catch (err) {
                    this.sendRaw(JSON.stringify({ type, requestId, data: { error: (err as Error).message } }));
                }
                return;
            }

            // 3. 通知型消息
            this.wsDispatcher.dispatchMessage(type, data);
        } else {
            console.warn('[WS] Unknown message type:', raw);
        }
    }

    /** 清理所有挂起请求 */
    private cleanupPendingRequests(reason: string): void {
        for (const requestId of this.wsDispatcher.getAllPendingRequestIds()) {
            const pending = this.wsDispatcher.getPendingRequest(requestId)!;
            clearTimeout(pending.timeoutId);
            pending.reject(new Error(reason));
            this.wsDispatcher.unregisterPendingRequest(requestId);
        }
    }

    /** 心跳 */
    private startHeartbeat(): void {
        this.stopHeartbeat();
        this.heartbeatTimer = setInterval(() => this.sendNotify('ping', { timestamp: Date.now() }), this.heartbeatInterval);
    }

    private stopHeartbeat(): void {
        if (this.heartbeatTimer) clearInterval(this.heartbeatTimer);
    }

    /** 底层发送 */
    private sendRaw(msg: string): void {
        if (this.socket?.connected) {
            this.socket.send(msg);
            console.log('[WS] Send:', msg);
        } else {
            console.warn('[WS] Connection lost, message dropped:', msg);
        }
    }
}
